package cli;

import static omero.rtypes.*;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
//import java.util.Iterator;
import java.util.List;

import javax.imageio.ImageIO;

import Glacier2.CannotCreateSessionException;
import Glacier2.PermissionDeniedException;
import exception.ServerConnectionException;
import omero.RType;
import omero.RLong;
//import ome.model.annotations.TagAnnotation;
//import ome.model.annotations.*;
import omero.ServerError;
import omero.client;
import omero.api.ExporterPrx;
import omero.api.IContainerPrx;
import omero.api.IMetadataPrx;
import omero.api.IQueryPrx;
import omero.api.IRoiPrx;
import omero.api.IUpdatePrx;
import omero.api.RawFileStorePrx;
import omero.api.RawPixelsStorePrx;
import omero.api.RenderingEngine;
import omero.api.RenderingEnginePrx;
import omero.api.RoiOptions;
import omero.api.RoiResult;
import omero.api.ServiceFactoryPrx;
import omero.model.Annotation;
import omero.model.AnnotationAnnotationLink;
import omero.model.Dataset;
import omero.model.DatasetAnnotationLink;
import omero.model.DatasetAnnotationLinkI;
import omero.model.DatasetI;
import omero.model.DatasetImageLink;
import omero.model.DatasetImageLinkI;
import omero.model.DoubleAnnotation;
import omero.model.DoubleAnnotationI;
import omero.model.FileAnnotation;
import omero.model.FileAnnotationI;
import omero.model.IObject;
import omero.model.Image;
import omero.model.ImageAnnotationLink;
import omero.model.ImageAnnotationLinkI;
import omero.model.ImageI;
import omero.model.LongAnnotation;
import omero.model.LongAnnotationI;
import omero.model.Mask;
import omero.model.MaskI;
import omero.model.OriginalFile;
import omero.model.OriginalFileI;
import omero.model.Project;
import omero.model.ProjectDatasetLink;
import omero.model.ProjectDatasetLinkI;
import omero.model.ProjectI;
import omero.model.Rect;
import omero.model.RectI;
import omero.model.Roi;
import omero.model.Shape;

import omero.model.RoiI;
import omero.romio.PlaneDef;
import omero.sys.EventContext;
import omero.sys.Parameters;
import omero.sys.ParametersI;
import pojos.AnnotationData;
import pojos.DatasetData;
import pojos.DoubleAnnotationData;
//import pojos.DoubleAnnotationData;
import pojos.FileAnnotationData;
import pojos.ImageData;
import pojos.MaskData;
import pojos.PixelsData;
import pojos.ProjectData;
import pojos.ROIData;
import pojos.RatingAnnotationData;
import pojos.RectangleData;
import pojos.ShapeData;
import pojos.TagAnnotationData;

/**
 * Setup connections between omero.client and omero.server
 * @author Peter
 */
public class OmeroServer {

	// top secret username and password
	private static String username;
	private static String password;
	private static String hostName;
	private static String portString;
	
	private static client unsecureClient = null;
	private static ServiceFactoryPrx session = null;

	private static int INC = 262144;   //INC = 262144;

	/**
	 * Convenience method because 2 times addFile doesnt work
	 * 
	 * @param fileName1
	 * @param saveAsFilename1
	 * @param generatedSha1
	 * @param fileName2
	 * @param saveAsFilename2
	 * @param generatedSha2
	 * @param dataset
	 * @return
	 * @throws ServerError
	 * @throws IOException
	 */
	public static  DatasetData  addTwoFilesToDataset( String fileName1, String saveAsFilename1, String generatedSha1, String fileName2, String saveAsFilename2, String generatedSha2, DatasetData dataset ) 
			throws ServerError, IOException  {
		File file1 = new File( fileName1 );
		String name1 = file1.getName();
		String absolutePath1 = file1.getAbsolutePath();
		String path1 = absolutePath1.substring(0, absolutePath1.length() - name1.length() );
	
		File file2 = new File( fileName2 );
		String name2 = file2.getName();
		String absolutePath2 = file2.getAbsolutePath();
		String path2 = absolutePath2.substring(0, absolutePath2.length() - name2.length() );

		//
		IUpdatePrx iUpdate = session.getUpdateService();
		
		OriginalFile originalFile1 = new OriginalFileI();
		if ( saveAsFilename1 != null ) {
			originalFile1.setName( rstring( saveAsFilename1 ) );
		} else { 
			originalFile1.setName( rstring(name1) );
		}
		originalFile1.setPath( rstring(path1) );
		originalFile1.setSize( rlong(file1.length() ) );
		originalFile1.setSha1( rstring( generatedSha1 ) );
//		originalFile.setMimetype( rstring( "des" ) );
		originalFile1 = (OriginalFile)iUpdate.saveAndReturnObject( originalFile1 );

		OriginalFile originalFile2 = new OriginalFileI();
		if ( saveAsFilename2 != null ) {
			originalFile2.setName( rstring( saveAsFilename2 ) );
		} else { 
			originalFile2.setName( rstring(name2) );
		}
		originalFile2.setPath( rstring(path2) );
		originalFile2.setSize( rlong(file2.length() ) );
		originalFile2.setSha1( rstring( generatedSha2 ) );
		originalFile2 = (OriginalFile)iUpdate.saveAndReturnObject( originalFile2 );

		RawFileStorePrx rawFileStore1 = session.createRawFileStore();
		rawFileStore1.setFileId( originalFile1.getId().getValue() );

		RawFileStorePrx rawFileStore2 = session.createRawFileStore();
		rawFileStore2.setFileId( originalFile2.getId().getValue() );
		
		FileInputStream stream = new FileInputStream( file1 );
		long pos = 0;
		int rlen = 0;
		byte[] buf = new byte[INC];
		ByteBuffer bbuf;
		while ( (rlen = stream.read(buf)) > 0 ) {
			 rawFileStore1.write(buf, pos, rlen);
			  pos += rlen;
			  bbuf = ByteBuffer.wrap(buf);
			  bbuf.limit(rlen);
		}
		stream.close();


		FileInputStream stream2 = new FileInputStream( file2 );
		pos = 0;
		rlen = 0;
		byte[] buf2 = new byte[INC];
		ByteBuffer bbuf2;
		while ( (rlen = stream2.read(buf)) > 0 ) {
			 rawFileStore2.write(buf, pos, rlen);
			  pos += rlen;
			  bbuf2 = ByteBuffer.wrap(buf);
			  bbuf2.limit(rlen);
		}
		stream2.close();
		
		originalFile1 = rawFileStore1.save();
		// Important to close the service
		rawFileStore1.close();

		originalFile2 = rawFileStore2.save();
		// Important to close the service
		rawFileStore2.close();

		//now we have an original File in DB and raw data uploaded.
		// We now need to link the Original file to the image using 
		// the File annotation object. That's the way to do it.
		FileAnnotation fa1 = new FileAnnotationI();
		FileAnnotation fa2 = new FileAnnotationI();
	
		fa1.setFile(originalFile1);
		fa1.setDescription(omero.rtypes.rstring("matlab generated file"));
		fa2.setFile(originalFile2);
		fa2.setDescription(omero.rtypes.rstring("roi image"));

		// save the file annotation.
//		fa1 = (FileAnnotation) iUpdate.saveAndReturnObject(fa1);
//		fa2 = (FileAnnotation) iUpdate.saveAndReturnObject(fa2);

		// now link the image and the annotation
		DatasetAnnotationLink link1 = new DatasetAnnotationLinkI();
		link1.setChild(fa1);
		link1.setParent(dataset.asDataset());
		
		DatasetAnnotationLink link2 = new DatasetAnnotationLinkI();
		link2.setChild(fa2);
		link2.setParent(dataset.asDataset());
		

		// save the link back to the server.
		List<IObject> links = new ArrayList<IObject>();
		links.add( link1 );
		links.add( link2 );
		iUpdate.saveAndReturnArray( links );
		
		// save the link back to the server.
//		IObject r1 =  session.getUpdateService().saveAndReturnObject(link1);
//		IObject r2 =  session.getUpdateService().saveAndReturnObject(link2);
		
//		link = (DatasetAnnotationLink)r;
//		DatasetData ds = new DatasetData( link.getParent() );
		return null;
	}

	/** 
	 * Get the image that belongs to the Region Of Interest (ROI)
	 * ROi must be square.
	 * 
	 */
	public static BufferedImage createImgFromRoi ( long roiId )  throws ServerError {
		//get the roi
		Roi roi = getRoiById( roiId);
		Rect rectangle = getShapeByRoiId( roiId );
		
		double x = rectangle.getX().getValue();
		double y = rectangle.getY().getValue();
		double w = rectangle.getWidth().getValue();
		double h = rectangle.getHeight().getValue();
		
		Image img = roi.getImage();
		long imgId = img.getId().getValue();
		BufferedImage bufImg;
		try {
			bufImg = renderImage( imgId);
			BufferedImage roiBufImg =  cropImage( bufImg, x, y, w, h );
			return roiBufImg;
		} catch (IOException e) {			
			e.printStackTrace();
			return null;
		}
		
	}
	public static ServiceFactoryPrx getSession() {
		return session;
	}
	
	public static client getClient() {
		return unsecureClient;
	}
	
	/**
	 * Open the connection from the client to the server
	 * @throws ServerConnectionException 
	 */
	
	/*
	public static void open() throws ServerConnectionException  {
		
		hostName = Settings.getServerUrl();
		portString = Settings.getServerPort();
		username = Settings.getUserName();
		password = Settings.getUserPassword();
		
		int port = Integer.valueOf( portString );
		unsecureClient = new client(hostName, port);
	
		try {
			Logging.log( "Connection", "Connecting to Omero Server " + hostName + "..." );
		    System.out.println("Connecting to Omero Server " + hostName + "..." );
			ServiceFactoryPrx entry = unsecureClient.createSession(username, password);
			Logging.log( "Connection", "Connected to Omero Server" );
		    System.out.println("You are connected");
			
			session = unsecureClient.getSession();
		
			
			//Retrieve the user id.			
			long userId = session.getAdminService().getEventContext().userId;
			long groupId = session.getAdminService().getEventContext().groupId;
			System.out.println( "Userid: " + userId + " groupid: " + groupId );
			
		} catch (CannotCreateSessionException e) {
			Logging.log( "Error", "CannotCreateSessionException" );
			throw new ServerConnectionException();
		} catch (PermissionDeniedException e) {
			Logging.log( "Error", "PermissionDeniedException" );
			throw new ServerConnectionException();
		} catch (ServerError e) {
			Logging.log( "Error", "Server Error" );
			throw new ServerConnectionException();
		}
	}
	*/
	
	
	/**
	 * Open the connection from the client to the server
	 * @throws ServerConnectionException 
	 */
	public static void open(String hostName, int port, String username, String password ) throws ServerConnectionException  {
		
		unsecureClient = new client(hostName, port);
	
		try {
			Logging.log( "Connection", "Connecting to Omero Server " + hostName + "..." );
		    System.out.println("Connecting to Omero Server " + hostName + "..." );
			ServiceFactoryPrx entry = unsecureClient.createSession(username, password);
			Logging.log( "Connection", "Connected to Omero Server" );
		    System.out.println("You are connected");
			
			session = unsecureClient.getSession();
		
			//Retrieve the user id.			
			long userId = session.getAdminService().getEventContext().userId;
			long groupId = session.getAdminService().getEventContext().groupId;
			System.out.println( "Userid: " + userId + " groupid: " + groupId );
			
		} catch (CannotCreateSessionException e) {
			Logging.log( "Error", "CannotCreateSessionException" );
			throw new ServerConnectionException();
		} catch (PermissionDeniedException e) {
			Logging.log( "Error", "PermissionDeniedException" );
			throw new ServerConnectionException();
		} catch (ServerError e) {
			Logging.log( "Error", "Server Error" );
			throw new ServerConnectionException();
		}
	}
	
	/**
	 * Close connection to Omero server
	 */
	public static void close() {
		//if unsecure client exists.
		if (unsecureClient != null) {
			unsecureClient.closeSession();
			Logging.log( "Connection", "Disconnected from Omero Server" );
		    System.out.println("You are now disconnected");
		}		
	}
	
	/**
	 * Get List of images that are contained in the dataset
	 * Return null if dataset with datasetId is not found
	 * @param dataSetId identifies the dataset
	 * @return Set<ImageData> collection of images or Null if dataset not found
	 * @throws ServerError when not connected
	 */
	public static Set<ImageData> getImagesInDataSet ( long dataSetId )  throws ServerError  {
		
		// ensure that there is a valid session
		if ( session == null ) {
			throw new ServerError();
		}
		
		IContainerPrx proxy = session.getContainerService();
		ParametersI param = new ParametersI();
		param.leaves(); //indicate to load the images

		List<IObject> results = proxy.loadContainerHierarchy(Dataset.class.getName(), Arrays.asList(dataSetId), param);
		if ( results.size() == 0 )
			return null;
		else {
			DatasetData dataset = new DatasetData( (Dataset) results.get(0) );
			Set<ImageData> images = dataset.getImages();
			return images;
		}
	}
	
	/**
	 * Get an image identified by imageId 
	 * 
	 * @param imageId identifies the image
	 * @return ImageData  or null if imageid is not found
	 * @throws ServerError
	 */
	public static ImageData getImage( long imageId ) throws ServerError {
		IContainerPrx proxy = session.getContainerService();
		List<Image> results = proxy.getImages(Image.class.getName(), Arrays.asList(imageId), new ParametersI());
		if (results.size() == 0 )
			return null;
		else { 
			ImageData image = new ImageData(results.get(0));
			
			return image;
		}
	}
	
	/**
	 * Add a file as attachment to the imageData in the omero server. Attachments are annotations.
	 * 
	 * @param fileName is full name of the file (like '/mips/db/40x.des' )
	 * @param generatedSha1 the SHA1 code of the file
	 * @param image to which the file will be attached
	 * @param namespace
	 * @throws ServerError
	 * @throws IOException
	 */
	public static void addFileToImage( String fileName, String generatedSha1, ImageData image, String namespace ) 
			throws ServerError, IOException   {
		addFileToImage( fileName, null, generatedSha1, image, namespace );
	}
	
	/**
	 * Add a file as attachment to the imageData in the omero server. Attachments are annotations.
	 * Can save the file with another name by using saveAsFilename 
	 * 
	 * @param fileName is full name of the file (like '/mips/db/40x.des' )
	 * @param saveAsFilename save file with this name. Use null to disable
	 * @param generatedSha1 the SHA1 code of the file
	 * @param image to which the file will be attached
	 * @param namespace 
	 * @throws ServerError
	 * @throws IOException
	 */
	public static  void  addFileToImage( String fileName, String saveAsFilename, String generatedSha1, ImageData image, String namespace ) 
			throws ServerError, IOException  {
		File file = new File( fileName );
		String name = file.getName();
		String absolutePath = file.getAbsolutePath();
		String path = absolutePath.substring(0, absolutePath.length() - name.length() );
	
		IUpdatePrx iUpdate = session.getUpdateService();
		OriginalFile originalFile = new OriginalFileI();
		if ( saveAsFilename != null ) {
			originalFile.setName( rstring( saveAsFilename ) );
		} else { 
			originalFile.setName( rstring(name) );
		}
		originalFile.setPath( rstring(path) );
		originalFile.setSize( rlong(file.length() ) );
		originalFile.setSha1( rstring( generatedSha1 ) );
//		originalFile.setMimetype( rstring( "des" ) );
		originalFile = (OriginalFile)iUpdate.saveAndReturnObject( originalFile );
		
		RawFileStorePrx rawFileStore = session.createRawFileStore();
		rawFileStore.setFileId( originalFile.getId().getValue() );
		
		FileInputStream stream = new FileInputStream( file );
		long pos = 0;
		int rlen;
		byte[] buf = new byte[INC];
		ByteBuffer bbuf;
		while ( (rlen = stream.read(buf)) > 0 ) {
			 rawFileStore.write(buf, pos, rlen);
			  pos += rlen;
			  bbuf = ByteBuffer.wrap(buf);
			  bbuf.limit(rlen);
		}
		stream.close();

		originalFile = rawFileStore.save();
		// Important to close the service
		rawFileStore.close();

		//now we have an original File in DB and raw data uploaded.
		// We now need to link the Original file to the image using 
		// the File annotation object. That's the way to do it.
		FileAnnotation fa = new FileAnnotationI();
	
		fa.setFile(originalFile);
		fa.setDescription(omero.rtypes.rstring("matlab generated file"));
		fa.setNs(omero.rtypes.rstring( namespace)); // The name space you have set to identify the file annotation.

		// save the file annotation.
		fa = (FileAnnotation) iUpdate.saveAndReturnObject(fa);

		// now link the image and the annotation
		ImageAnnotationLink link = new ImageAnnotationLinkI();
		link.setChild(fa);
		link.setParent(image.asImage());
		// save the link back to the server.
		link = (ImageAnnotationLink) iUpdate.saveAndReturnObject(link);
	}

	/**
	 * Add a file as attachment and a number to the Dataset in the omero server. Attachments are annotations.
	 * Can save the file with another name by using saveAsFilename 
	 * 
	 * @param fileName is full name of the file (like '/mips/db/40x.des' )
	 * @param saveAsFilename save file with this name. Use null to disable
	 * @param generatedSha1 the SHA1 code of the file
	 * @param number the number of the ROI image
	 * @param dataset to which the file will be attached
	 * @param namespace Use null to disable
	 * @throws ServerError
	 * @throws IOException
	 */
	public static  void  addFileAndNumberToDataset( String fileName, String saveAsFilename, String generatedSha1, long number, DatasetData dataset ) 
			throws ServerError, IOException  {
		File file = new File( fileName );
		String name = file.getName();
		String absolutePath = file.getAbsolutePath();
		String path = absolutePath.substring(0, absolutePath.length() - name.length() );
	
		IUpdatePrx iUpdate = session.getUpdateService();
		OriginalFile originalFile = new OriginalFileI();
		if ( saveAsFilename != null ) {
			originalFile.setName( rstring( saveAsFilename ) );
		} else { 
			originalFile.setName( rstring(name) );
		}
		originalFile.setPath( rstring(path) );
		originalFile.setSize( rlong(file.length() ) );
		originalFile.setSha1( rstring( generatedSha1 ) );
//		originalFile.setMimetype( rstring( "des" ) );
		originalFile = (OriginalFile)iUpdate.saveAndReturnObject( originalFile );
		
		RawFileStorePrx rawFileStore = session.createRawFileStore();
		rawFileStore.setFileId( originalFile.getId().getValue() );
		
		FileInputStream stream = new FileInputStream( file );
		long pos = 0;
		int rlen;
		byte[] buf = new byte[INC];
		ByteBuffer bbuf;
		while ( (rlen = stream.read(buf)) > 0 ) {
			 rawFileStore.write(buf, pos, rlen);
			  pos += rlen;
			  bbuf = ByteBuffer.wrap(buf);
			  bbuf.limit(rlen);
		}
		stream.close();

		originalFile = rawFileStore.save();
		// Important to close the service
		rawFileStore.close();

		//now we have an original File in DB and raw data uploaded.
		// We now need to link the Original file to the image using 
		// the File annotation object. That's the way to do it.
		FileAnnotation fa = new FileAnnotationI();	
		fa.setFile(originalFile);
		fa.setDescription(omero.rtypes.rstring("matlab generated file"));

		// save the file annotation.
//		fa = (FileAnnotation) iUpdate.saveAndReturnObject(fa);

		LongAnnotation la = new LongAnnotationI();		
		la.setLongValue( rlong(number) );
		la.setDescription(omero.rtypes.rstring("roiId that was used for the search"));

		// save the file annotation.
//		IUpdatePrx iUpdate = session.getUpdateService();
//		la = (LongAnnotation) iUpdate.saveAndReturnObject(la);

		// now link the file and the dataset
		DatasetAnnotationLink link1 = new DatasetAnnotationLinkI();
		link1.setChild(fa);
		link1.setParent(dataset.asDataset());

		// now link the number and the dataset
		DatasetAnnotationLink link2 = new DatasetAnnotationLinkI();
		link2.setChild(la);
		link2.setParent(dataset.asDataset());

		// store in list
		List<IObject> listOfLinks = new ArrayList<IObject>();
		listOfLinks.add( link1 );
		listOfLinks.add( link2 );
		
		// save the link back to the server.
		iUpdate.saveAndReturnArray( listOfLinks );
	}

	/**
	 * Add a number (long) as annotation the the Dataset in the omero server.
	 * 
	 * @param number that will be attached to the dataset
	 * @param dataset 
	 * @return DatasetData 
	 * @throws ServerError 
	 *
	 */
	public static DatasetData addNumberToDataset(  long number, DatasetData dataset ) 
			throws ServerError 
		{
		LongAnnotation la = new LongAnnotationI();		
		la.setLongValue( rlong(number) );
		la.setDescription(omero.rtypes.rstring("roiId that was used for the search"));

		// save the file annotation.
		IUpdatePrx iUpdate = session.getUpdateService();
		la = (LongAnnotation) iUpdate.saveAndReturnObject(la);

		// now link the image and the annotation
		DatasetAnnotationLink link = new DatasetAnnotationLinkI();
		link.setChild(la);
		link.setParent(dataset.asDataset());

		// save the link back to the server.
		IObject r =  session.getUpdateService().saveAndReturnObject(link);
		link = (DatasetAnnotationLink)r;
		DatasetData ds = new DatasetData( link.getParent() );
		return ds;
	}
	
	/**
	 * Add a file as attachment to the Dataset in the omero server. Attachments are annotations.
	 * Can save the file with another name by using saveAsFilename 
	 * 
	 * @param fileName is full name of the file (like '/mips/db/40x.des' )
	 * @param saveAsFilename save file with this name. Use null to disable
	 * @param generatedSha1 the SHA1 code of the file
	 * @param dataset to which the file will be attached
	 * @param namespace Use null to disable
	 * @throws ServerError
	 * @throws IOException
	 */
	public static  DatasetData  addFileToDataset( String fileName, String saveAsFilename, String generatedSha1, DatasetData dataset ) 
			throws ServerError, IOException  {
		File file = new File( fileName );
		String name = file.getName();
		String absolutePath = file.getAbsolutePath();
		String path = absolutePath.substring(0, absolutePath.length() - name.length() );
	
		IUpdatePrx iUpdate = session.getUpdateService();
		OriginalFile originalFile = new OriginalFileI();
		if ( saveAsFilename != null ) {
			originalFile.setName( rstring( saveAsFilename ) );
		} else { 
			originalFile.setName( rstring(name) );
		}
		originalFile.setPath( rstring(path) );
		originalFile.setSize( rlong(file.length() ) );
		originalFile.setSha1( rstring( generatedSha1 ) );
//		originalFile.setMimetype( rstring( "des" ) );
		originalFile = (OriginalFile)iUpdate.saveAndReturnObject( originalFile );
		
		RawFileStorePrx rawFileStore = session.createRawFileStore();
		rawFileStore.setFileId( originalFile.getId().getValue() );
		
		FileInputStream stream = new FileInputStream( file );
		long pos = 0;
		int rlen;
		byte[] buf = new byte[INC];
		ByteBuffer bbuf;
		while ( (rlen = stream.read(buf)) > 0 ) {
			 rawFileStore.write(buf, pos, rlen);
			  pos += rlen;
			  bbuf = ByteBuffer.wrap(buf);
			  bbuf.limit(rlen);
		}
		stream.close();

		originalFile = rawFileStore.save();
		// Important to close the service
		rawFileStore.close();

		//now we have an original File in DB and raw data uploaded.
		// We now need to link the Original file to the image using 
		// the File annotation object. That's the way to do it.
		FileAnnotation fa = new FileAnnotationI();
	
		fa.setFile(originalFile);
		fa.setDescription(omero.rtypes.rstring("matlab generated file"));

		// save the file annotation.
//		fa = (FileAnnotation) iUpdate.saveAndReturnObject(fa);

		// now link the image and the annotation
		DatasetAnnotationLink link = new DatasetAnnotationLinkI();
		link.setChild(fa);
		link.setParent(dataset.asDataset());
		// save the link back to the server.
		IObject r =  session.getUpdateService().saveAndReturnObject(link);
		link = (DatasetAnnotationLink)r;
//		DatasetData ds = new DatasetData( link.getParent() );
		return null;
	}

	
	
	/**
	 * Download the File annotation (attachment) from the omero server and store it on the local machine
	 * @param fileName is full name of where the file is stored
	 * @param annotation the attachment on the omero server
	 * @throws ServerError
	 * @throws IOException
	 */
	public static void saveAttachment(  String fileName, FileAnnotation annotation ) throws ServerError, IOException  {
		FileAnnotationData fa;
		RawFileStorePrx store = session.createRawFileStore();
		int index = 0;
		File file = new File( fileName ); //This file should be there (?)
		FileOutputStream stream = new FileOutputStream( file );
		OriginalFile of;
		fa = new FileAnnotationData( annotation );
		//The id of the original file
		of = annotation.getFile();
		
		//of = annotation.getFile( fa.getFileID() );
		store.setFileId( fa.getFileID() );
		int offset = 0;
		long size = of.getSize().getValue();
		//name of the file
		//of.getName().getValue();
		try {
			for ( offset = 0; (offset+INC) < size; ) {
				stream.write( store.read( offset, INC ) );
				offset += INC;
			}
		} finally {
			stream.write( store.read( offset,  (int) (size-offset ) ) );
			stream.close();
		}
	}
	
	/**
	 * Generate TIF from image for exporting
	 * @param imageId
	 * @return
	 * @throws Exception
	 */
	public static byte[] generateOmeTif( long imageId ) throws Exception {
		ExporterPrx exporter = session.createExporter();
		exporter.addImage( imageId );
		
		long size = exporter.generateTiff();
		byte[] values = exporter.read( 0, (int) size );
		System.out.println("OME-TIF Image with id " + imageId + " generated" );
		exporter.close();
		return values;
	}

	public static BufferedImage generateTif( long imageId ) throws Exception {
		ImageData img = getImage( imageId);
		PixelsData pxd = null;
		pxd = img.getDefaultPixels();
		BufferedImage bi = new BufferedImage( 20, 20, BufferedImage.TYPE_INT_RGB );
		Graphics2D g = (Graphics2D) bi.getGraphics();
		g.setColor(Color.BLACK );
		g.drawRect( 0, 0, 20, 20 );
		return bi;
		
		
	}
	/**
	 * Get ROI identified by roiId. Can be used to check whether 
	 * the roiId is valid
	 * @param roiId
	 * @return RoiI roi image or null when roiId was not found
	 * @throws ServerError
	 */
	public static RoiI getRoiById( long roiId ) throws ServerError {
        IQueryPrx q = session.getQueryService();
        String query_string = "SELECT i FROM Roi i WHERE  id= :id";
        ParametersI p = new ParametersI();
        p.add("id", rlong( roiId ) );
        //p.add("namedParameter", rstring("%mage%"));
        List<IObject> results = q.findAllByQuery(query_string, p);
        //System.out.println("Number of objects that match the query: " + results.size() );
        if ( results.size() == 0 ) 
        	return null;
        else {
        	RoiI roi = (RoiI) results.get(0);
        	//System.out.println( roi.getId().getValue() );
        	return roi;
        }    
	}
	
	public static Rect getRoiForImage( long imgId ) throws ServerError {	
		RoiResult r = session.getRoiService().findByImage( imgId,  new RoiOptions()  );
		List<Roi> rois = r.rois;
		List<Shape> list;
		Iterator<Roi> j = rois.iterator();
		Roi roi = new RoiI();
		Rect rect1 = new RectI();
		
		while ( j.hasNext() ) {
			roi = j.next();
			list = roi.copyShapes();
			for ( int n = 0; n < list.size(); n++ ) {
				Shape s1 = list.get( n );
				//check for rectangle ROI
				if ( s1 instanceof Rect ) {
					rect1 = (Rect) s1;
				//	rect1.getX();
				//	rect1.getY();
					System.out.println( "x: " + rect1.getX().getValue() + ", y: " + rect1.getY().getValue() + " - " + rect1.getWidth().getValue() + " - " + rect1.getHeight().getValue() );					
				}
			}
		}
		
		return rect1;
	}
	
	/**
	 * Checks whether the omero-image has .des and .frm attachment-files
	 * @return true when des and frm files are attached to the image
	 */
	private  static boolean hasIndexAttachments( ImageData image ) {
		List<IObject> annotations;
		String fileBody = FileOperations.filenameBody( image.getName() ).toLowerCase();
		boolean desFile = false;
		boolean frmFile = false;		
		try {		
			annotations = OmeroServer.getAnnotations(image.getId() );
			Iterator<IObject> i = annotations.iterator();
			
			IObject annotation;
			while ( i.hasNext() ) {
				annotation = i.next();
				if ( annotation instanceof FileAnnotation ) {
					FileAnnotation fileAttachment = (FileAnnotation) annotation;
					OriginalFile file = fileAttachment.getFile();
					String fullname = file.getName().getValue();
					String mime = FileOperations.filenameExtension( fullname).toLowerCase();
					String body = FileOperations.filenameBody( fullname ).toLowerCase();
					if ( mime.equals( "des") && body.equals( fileBody ) ) desFile = true;
					if ( mime.equals( "frm") && body.equals( fileBody ) ) frmFile = true;
				}
			}
			if ( desFile && frmFile ) 
				return true;
			else 
				return false;
		} catch (ServerError e) {
			e.printStackTrace();
		}	
		return true;
	}
	
	/**
	 * Get the .des and .frm attachment-files for an Omero image
	 * @param image 
	 * @return Set of des and frm file attachments or null when no des and frm files are attached to the image
	 */
	public static List<FileAnnotation> getIndexAttachments( ImageData image ) {
		List<IObject> annotations;
		String fileBody = FileOperations.filenameBody( image.getName() ).toLowerCase();
		List<FileAnnotation> fileAttachments = new ArrayList<FileAnnotation>(); 
		int desFile = 0;
		int frmFile = 0;		
		try {		
			annotations = OmeroServer.getAnnotations(image.getId() );
			Iterator<IObject> i = annotations.iterator();
			
			IObject annotation;
			while ( i.hasNext() ) {
				annotation = i.next();
				if ( annotation instanceof FileAnnotation ) {
					FileAnnotation fileAttachment = (FileAnnotation) annotation;
					OriginalFile file = fileAttachment.getFile();
					String fullname = file.getName().getValue();
					String mime = FileOperations.filenameExtension( fullname).toLowerCase();
					String body = FileOperations.filenameBody( fullname ).toLowerCase();
					if ( mime.equals( "des") && body.equals( fileBody ) ) {
						fileAttachments.add( fileAttachment );
						desFile++;
					}
					if ( mime.equals( "frm") && body.equals( fileBody ) ) { 
						fileAttachments.add( fileAttachment );
						frmFile++;
					}
				}
			}
			if ( desFile == 1  && frmFile == 1 ) 
				return fileAttachments;
			else 
				return null;
		} catch (ServerError e) {
			e.printStackTrace();
			return null;
		}	
	}

	/**
	 * Attaches the result of the search as a rankingvalue to the image<br/>
	 * Use namespace to identify to which search the values belong
	 * If a ranking value exists it will be updated, if none exist it will be created
	 * @param rankingValue 
	 * @param namespace  used to identify the search
	 * @param imageId
	 */
	public static void setImageRankingValue( long imageId, double rankingValue, String namespace  ) throws ServerError {
	
		ImageData image = getImage( imageId );
			
		// get/check if there is already a rating 
		DoubleAnnotation existingRating = getRankingValue( imageId, namespace );
		if ( existingRating != null ) {
			existingRating.setDoubleValue( rdouble(rankingValue ) );
			IObject r = session.getUpdateService().saveAndReturnObject( existingRating );
		} else {
			DoubleAnnotation rating = new DoubleAnnotationI();

			rating.setDoubleValue(rdouble(rankingValue) );
			rating.setNs( rstring( namespace ) );

			// now link the image and the annotation
			ImageAnnotationLink link = new ImageAnnotationLinkI();
			link.setChild( rating );

			link.setParent(image.asImage());
			// save the link back to the server.
			IObject r  = session.getUpdateService().saveAndReturnObject(link);
		}
	}
	
	/**
	 * Get the annotation for an image with specified namespace
	 * 
	 * @param imageId identifies the image
	 * @param namespace identifies the namespace
	 * @return List<IObject>
	 * @throws ServerError
	 */
	public static List<IObject> getAnnotations( long imageId, String namespace ) 
			throws ServerError {
		
		List<IObject> returnvalues = new ArrayList<IObject>();

		if ( session == null ) {
			throw new ServerError();
		}
		List<IObject> results = getAnnotations( imageId );
		if ( results == null ) return null;
		
		Iterator<IObject> i = results.iterator();
		Annotation annotation;
		while ( i.hasNext() ) {
			annotation = (Annotation) i.next();
			annotation.getNs();
			if ( annotation.getNs() != null ) { 
				if ( annotation.getNs().getValue().equals( namespace ) ) {
					returnvalues.add( annotation );
				}
			}
		}
		return returnvalues;
	}
	
	
	/**
	 * Get the annotation for an image
	 * 
	 * @param imageId identifies the image
	 * @return List<IObject>
	 * @throws ServerError
	 */
	public static List<IObject> getAnnotations( long imageId ) 
			throws ServerError {

		// ensure that there is a valid session
		if ( session == null ) {
			throw new ServerError();
		}
		ArrayList<Long> imageIds = new ArrayList<Long>();
		imageIds.add( imageId);
		
		ArrayList<String> annotationTypes = new ArrayList<String>();
			
		ArrayList annotationIds = new ArrayList();
		ParametersI param = new ParametersI();
		
		IMetadataPrx proxy = session.getMetadataService();
		Map<Long,List<IObject> > annotations = proxy.loadAnnotations( "Image", imageIds, annotationTypes, annotationIds, param );
		if ( annotations.size() == 0 )
			return null;
		return annotations.get(imageId);
	}
	
		/**
	 * Get a the first double annotation (double annotation = ranking). <br/>
	 * Return null if no double annotations found
	 * @param imageId
	 * @return DoubleAnnotation of ranking value or null if no ranking value found
	 * @throws ServerError if 
	 */
	private static DoubleAnnotation getRankingValue( long imageId, String namespace ) throws ServerError {
		
		List<IObject> annotations = OmeroServer.getAnnotations( imageId, namespace );
		Iterator<IObject> i = annotations.iterator();
		int cnt = 0;
		IObject annotation;
		DoubleAnnotation doubleAnn = new DoubleAnnotationI();
		while ( i.hasNext() ) {
			annotation = i.next();
			if ( annotation instanceof DoubleAnnotation ) {
				doubleAnn = (DoubleAnnotation)annotation;
				cnt++;
			}
		}
		if (cnt == 1 ) 
			return doubleAnn;
		else if ( cnt > 1 ) {
			//delete all values//
			deleteRankings( imageId );
			return null;
		}
		return null;
	}
	
	public static Double getImageRankingValue( long imageId, String namespace ) throws ServerError {

		List<IObject> annotations;

		Double value = 0.0;
		try {		
			annotations = OmeroServer.getAnnotations( imageId, namespace );
			Iterator<IObject> i = annotations.iterator();
			
			int cnt = 0;
			IObject annotation;
			while ( i.hasNext() ) {
				annotation = i.next();
				if ( annotation instanceof DoubleAnnotation ) {
					DoubleAnnotation valueAnnotation = (DoubleAnnotation) annotation;
					value += valueAnnotation.getDoubleValue().getValue();
					cnt++;
				}
			}
			return value;
		} catch (ServerError e) {
			e.printStackTrace();
			return null;
		}	
	}
	
	
	/**
	 * Remove all the double annotations (ranking info) that are linked to an image
	 * Max number of double annotations should be one. When two or more are detected
	 *  all of them are deleted (Double annotations are not displayed or can not be
	 *   deleted by Insight
	 */
	private static void deleteRankings( long imageId ) throws ServerError {

		List<IObject> annotations;
		try {		
			annotations = OmeroServer.getAnnotations( imageId );
			Iterator<IObject> i = annotations.iterator();
			
			int cnt = 0;
			IObject annotation;
			while ( i.hasNext() ) {
				annotation = i.next();
				if ( annotation instanceof DoubleAnnotation ) {
					DoubleAnnotation valueAnnotation = (DoubleAnnotation) annotation;
					IObject r = session.getUpdateService().saveAndReturnObject( valueAnnotation );
					session.getUpdateService().deleteObject( r );
					cnt++;
				}
			}
		} catch (ServerError e) {
			e.printStackTrace();
		}			
	}
	/**
	 * Attaches a star rating to an image. Rating ranges from 1 to 5. Use a value of 0 to clear the rating
	 * @param rating values from 1 to 5. Value of 0 to clear the star rating
	 * @param imageId to identify the image
	 * @throws ServerError 
	 */
	public static void setImageStarRating( long imageId, int rating ) throws ServerError {
		
		ImageData image = getImage( imageId );
		
		//remove existing star ratings
		removeStarRatings( imageId );
		
		RatingAnnotationData starRating = new RatingAnnotationData();
		starRating.setRating( rating );
		
		ImageAnnotationLink link = new ImageAnnotationLinkI();
		link.setChild( starRating.asAnnotation() );

		link.setParent(image.asImage());
		// save the link back to the server.
		IObject r  = session.getUpdateService().saveAndReturnObject(link);

	}
	
	/**
	 * removeStarRatings for specified imageId
	 * @param imageId
	 * @throws ServerError 
	 */
	private static void removeStarRatings( long imageId ) throws ServerError {	
		List<IObject> annotations;
		try {		
			annotations = OmeroServer.getAnnotations( imageId );
			Iterator<IObject> i = annotations.iterator();
			IObject annotation;
			int cnt = 0;
			while ( i.hasNext() ) {
				annotation = i.next();
				if ( annotation instanceof LongAnnotation ) {
					
					
					//IUpdatePrx service = session.getUpdateService();
					//service.deleteObject(annotation );
					cnt++;
				}
			}
			System.out.println( "Number of star ratings deleted: " + cnt);
		} catch (ServerError e) {
			e.printStackTrace();
		}	
		
	}

	public static void getImageStarRating( long imageId ) {
		List<IObject> annotations;

		try {		
			annotations = OmeroServer.getAnnotations( imageId );
			Iterator<IObject> i = annotations.iterator();
			
			int cnt = 0;
			IObject annotation;
			while ( i.hasNext() ) {
				annotation = i.next();
				if ( annotation instanceof LongAnnotation ) {
					cnt++;
				}
			}
			System.out.println( "Class - "  + cnt );
		} catch (ServerError e) {
			e.printStackTrace();
		}	
	}
	
	private IObject findImageAnnotationLink( long parentID, long childID)	{
		try {
			IQueryPrx service = session.getQueryService();
			String sql = "select link from ImageAnnotationLink as link where " +
					"link.parent.id = :parentID"; 
			Parameters p = new ParametersI();
			p.map = new HashMap<String, RType>();
			p.map.put("parentID", omero.rtypes.rlong(parentID));
			if (childID >= 0) {
				sql += " and link.child.id = :childID";
				p.map.put("childID", omero.rtypes.rlong(childID));
			}
			return service.findByQuery(sql, p);
		} catch ( ServerError e) {
			System.out.println( e.toString() + "Cannot retrieve the requested link for "+
					"parent ID: "+parentID+" and child ID: "+childID);		
			return null;
		}
	}
	
	public void getImagePixels( long imageId ) throws ServerError {
		ImageData imageData = getImage( imageId );
		PixelsData pixels = imageData.getDefaultPixels();
		int sizeZ = pixels.getSizeZ();
		int sizeT = pixels.getSizeT();
		int sizeC = pixels.getSizeC();
		long pixelsId = pixels.getId();
		RawPixelsStorePrx store = session.createRawPixelsStore();
		store.setPixelsId( pixelsId, false );
		for ( int z = 0; z < sizeZ; z++ ) {
			for ( int t = 0; t < sizeT; t++ ) {
				for ( int c = 0; c < sizeC; c++ ) {
					byte[] plane = store.getPlane(z, c, t );
					
				}
			}
		}
		store.close();
	}
	
	
	public static BufferedImage renderImage( long imageId ) throws IOException, ServerError {
		ImageData image = getImage( imageId );
		PixelsData pixels = image.getDefaultPixels();
		long pixelsId = pixels.getId();
		RenderingEnginePrx proxy = session.createRenderingEngine();
		proxy.lookupPixels(pixelsId);
		if (!(proxy.lookupRenderingDef(pixelsId))) {
			proxy.resetDefaults();
			proxy.lookupRenderingDef(pixelsId);
		}
		proxy.load();
		// Now can interact with the rendering engine.
//		Peter made value true instead of false
		proxy.setActive(0, Boolean.valueOf( true ));
	
		// to render the image uncompressed
		PlaneDef pDef = new PlaneDef();
		pDef.z = 0;
		pDef.t = 0;
		pDef.slice = omero.romio.XY.value;
		//render the data uncompressed.
//		int[] uncompressed = proxy.renderAsPackedInt(pDef);
		byte[] compressed = proxy.renderCompressed(pDef);

		//Create a buffered image
		ByteArrayInputStream stream = new ByteArrayInputStream(compressed);
		BufferedImage bufferedImage = ImageIO.read(stream);
		// Close
		proxy.close();
		
		return bufferedImage;
	}


	/**
	 * Returns the first rectangular shape that is associated with the ROI identified by roiId
	 * @param roiId
	 * @return Rect
	 * @throws ServerError
	 */
	public static Rect getShapeByRoiId( long roiId ) throws ServerError {
		RoiResult r = session.getRoiService().findByRoi(roiId, new RoiOptions() );
		List<Roi> rois = r.rois;
		List<Shape> list;
		Iterator<Roi> j = rois.iterator();
		Roi roi = new RoiI();
		Rect rect1 = new RectI();
		
		while ( j.hasNext() ) {
			roi = j.next();
			list = roi.copyShapes();
			for ( int n = 0; n < list.size(); n++ ) {
				Shape s1 = list.get( n );
				//check for rectangle ROI
				if ( s1 instanceof Rect ) {
					rect1 = (Rect) s1;
					System.out.println( "ROI: x: " + rect1.getX().getValue() + ", y: " + rect1.getY().getValue() + ", w: " + rect1.getWidth().getValue() + ", h: " + rect1.getHeight().getValue() );					
				}
			}
		}	
		return rect1;
	}
	/**
	 * Retrieve the current proje
	 * @return
	 * @throws ServerError 
	 */
	public static Long getProjectId( ) throws ServerError {
		IContainerPrx proxy = session.getContainerService();
		ParametersI param = new ParametersI();
		EventContext context = session.getAdminService().getEventContext();
		String username = context.userName;
		long userId = session.getAdminService().getEventContext().userId;
		param.exp(omero.rtypes.rlong(userId));
		List<IObject> results = proxy.loadContainerHierarchy( Project.class.getName(), new ArrayList<Long>(), param);
		//for the moment just get the first project for the user
		if( results.size() < 1 ) {
			System.out.println("Ensure that your dataset is attached to a project" );
			return null;
		}
		Project project = (Project) results.get( 0 );
		return project.getId().getValue();		
	}
	
	
	/**
	 * Create a new dataset
	 * @param name for the new dataset
	 * @param projectId of the project to which the dataset belongs
	 * @return DatasetData
	 * @throws ServerError when not logged in
	 */
	public static DatasetData createDataset( String name, long projectId ) throws ServerError {	
		//Create new dataset
		DatasetData dataset = new DatasetData();
		dataset.setName("Search results");
		dataset.setDescription("results of the search");

		IUpdatePrx iUpdate = session.getUpdateService();
		//link dataset to project
		ProjectDatasetLink link = new ProjectDatasetLinkI();
		link.setChild(dataset.asDataset() );
		link.setParent(new ProjectI(projectId, false));
		IObject r = session.getUpdateService().saveAndReturnObject(link);
		//retrieve updated dataset
		link = (ProjectDatasetLink) r;
		return new DatasetData( link.getChild() );
	}
	/**
	 * Cretate a link from dataset to project
	 * 
	 * @param dataset
	 * @param projectId
	 * @return
	 * @throws ServerError
	 */
	public static DatasetData linkDatasetToProject( DatasetData dataset, long projectId ) throws ServerError {
		IUpdatePrx iUpdate = session.getUpdateService();
		//link dataset to project
		ProjectDatasetLink link = new ProjectDatasetLinkI();
		link.setChild(dataset.asDataset() );
		link.setParent(new ProjectI(projectId, false));
		IObject r = session.getUpdateService().saveAndReturnObject(link);
		//retrieve updated dataset
		link = (ProjectDatasetLink) r;
		return new DatasetData( link.getChild() );
	}
	
	/**
	 * Add the image to the dataset
	 * @param datasetId
	 * @param imageId
	 * @throws ServerError
	 */
	public static void addImageToDataset( long datasetId, long imageId ) throws ServerError {
		DatasetImageLink link = new DatasetImageLinkI();
		ImageData image = getImage( imageId);
		link.setChild( image.asImage() );
		link.setParent( new DatasetI( datasetId, false ));
		IObject r = session.getUpdateService().saveAndReturnObject( link );
	}
	
	/**
	 * Crops an image with the with a rectangle specified by x, y, width and height
	 * @param src   
	 * @param x x-coordinate of the rectangle
	 * @param y y-coordinate of the rectangle
	 * @param w widht of the rectangle
	 * @param h height of the rectangle
	 * @return BufferedImage
	 */
	private static BufferedImage cropImage(BufferedImage src, double x, double y, double w, double h ) {
		
		BufferedImage dest = src.getSubimage( (int)x, (int)y, (int)w, (int)h );

		return dest; 
	}

}

